const HtmlWebpackPlugin = require("html-webpack-plugin");

/**
 * 1. 找到异步加载的代码块
 * 2. 对每个异步的代码块，生成 <link rel="preload" as="script" href="http://localhost:9999/395.9e689745.js">
 * 3. 把这个 link 的标签添加到 html 页面
 */
class PreloadWebpackPlugin {
  constructor(options) {
    this.options = options || {};
    this.preloadSources = new Set();
  }

  apply(compiler) {
    compiler.hooks.compilation.tap(
      "RequireContextPlugin",
      (compilation, { contextModuleFactory, normalModuleFactory }) => {
        normalModuleFactory.hooks.parser
          .for("javascript/auto")
          .tap("RequireContextPlugin", (parser, parserOptions) => {
            parser.hooks.importCall.tap("ImportParserPlugin", (expr) => {
              const { options: importOptions } = parser.parseCommentOptions(
                expr.range
              );
              // import(/* webpackPreload: true */ "./hello")，读取到解析后的 webpackPreload 值
              if (importOptions && importOptions.webpackPreload) {
                const source = expr.source.value;
                this.preloadSources.add(source);
                // source 是 import("./hello") 这了的 ./hello
                console.log("source", source);
                // 设置为 preload
                this.preload = "preload";
              }
            });
          });
      }
    );

    compiler.hooks.compilation.tap("PreloadWebpackPlugin", (compilation) => {
      HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
        "PreloadWebpackPlugin",
        (html, callback) => {
          this.generateLinks(compilation, html, callback);
        }
      );
    });

    compiler.hooks.compilation.tap("PreloadWebpackPlugin", (compilation) => {
      HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tap(
        "PreloadWebpackPlugin",
        (html) => {
          const { resourceHints } = this;
          if (resourceHints) {
            html.assetTags.styles = [
              ...resourceHints,
              ...html.assetTags.styles,
            ];
          }
          return html;
        }
      );
    });
  }

  generateLinks(compilation, html, callback) {
    const { rel, include } = this.options;
    let chunks = [...compilation.chunks];
    if (include === undefined || include === "asyncChunks") {
      // chunk.canBeInitial() 返回 true 是入口代码块
      chunks = chunks
        .filter((chunk) => !chunk.canBeInitial())
        .filter((chunk) => {
          // chunk.name 是 webpackChunkName ： import(/* webpackPreload: true, webpackChunkName: "aaa" */ "./hello")
          const module = chunk.modulesIterable.values();
          const { value } = module.next();
          // 获取到 ./hello 是 import("./helle") 括号中的
          const rawRequest = value.rawRequest;

          return this.preloadSources.has(rawRequest);
        }); // 留下异步代码块
    }
    const allFiles = chunks.reduce((set, chunk) => {
      return set.add(...chunk.files);
    }, new Set());

    const links = [];
    for (const file of allFiles.values()) {
      links.push({
        tagName: "link",
        attributes: {
          rel: this.preload, // preload prefetch
          href: file,
          as: "script",
        },
      });
    }
    this.resourceHints = links;
    callback();
  }
}

module.exports = PreloadWebpackPlugin;
